package org.dru.clay.resolver;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;

import org.dru.clay.logger.Logger;
import org.dru.clay.logger.LoggerFactory;
import org.dru.clay.respository.Configuration;
import org.dru.clay.respository.Repository;
import org.dru.clay.respository.ResolveResult;
import org.dru.clay.respository.artifact.Artifact;
import org.dru.clay.respository.artifact.Group;
import org.dru.clay.respository.artifact.Module;
import org.dru.clay.respository.artifact.UnresolvedArtifact;
import org.dru.clay.respository.dependency.Dependency;
import org.dru.clay.respository.transport.Transport;

public class DependencyResolverImpl implements DependencyResolver {
	private static final int RESOLVE_THREAD_COUNT = 5;

	private final Logger logger = LoggerFactory.getLogger(DependencyResolverImpl.class);

	private final Repository repository;
	private final Transport transport;
	private final DirectoryLayout layout;

	private final Object lock = new Object();
	private int count;
	
	final Set<Artifact> visitedArtifacts = new HashSet<Artifact>();
	final Queue<ConfigurationContext> resolveQueue = new LinkedBlockingDeque<ConfigurationContext>();
	final List<File> downloadedFiles = new ArrayList<File>();
	
	ExecutorService executorService = null;

	public DependencyResolverImpl(Repository repository, Transport transport) {
		this.repository = repository;
		this.transport = transport;
		this.layout = new FlatDirectoryLayout();
	}
	
	@Override
	public void begin() {
		if (executorService != null) {
			executorService.shutdownNow();
		}
		downloadedFiles.clear();
		executorService = Executors.newFixedThreadPool(RESOLVE_THREAD_COUNT);
	}

	@Override
	public Collection<File> end() {
		try {
			synchronized (lock) {
				while (count > 0) {
					lock.wait(0L);
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			// TODO: All list might not have been downloaded.
		} finally {
			visitedArtifacts.clear();
			resolveQueue.clear();
			count = 0;
			
			executorService.shutdownNow();
			transport.cleanup();
		}
		
		return downloadedFiles;
	}

	@Override
	public void resolve(final File directory, String configuration, Collection<Dependency> dependencies) {
		filterDependencies(Collections.singleton(configuration), dependencies, resolveQueue);

		while (resolveQueue.peek() != null) {
			final ConfigurationContext context = resolveQueue.poll();
			final Dependency dependency = context.getDependency();
			final Group group = dependency.getGroup();
			final UnresolvedArtifact unresolved = dependency.getArtifact();

			final Artifact resolveArtifact = repository.lookup(transport, group, unresolved);

			logger.info("Resolved %s -> %s", unresolved, resolveArtifact);

			// TODO: Handle version conflicts
			if (visitedArtifacts.contains(resolveArtifact)) {
				// artifact already visited
				continue;
			}
			visitedArtifacts.add(resolveArtifact);

			final Module resolveModule = new Module(group, resolveArtifact);
			final ResolveResult resolveResult = repository.resolve(transport, resolveModule);

			for (Configuration config : resolveResult.getConfigurations()) {
				if (!context.getConfigurations().contains(config.getName())) {
					// the configuration isn't used in this resolve
					continue;
				}

				synchronized (lock) {
					count += config.getArtifacts().size();
				}

				for (final Artifact artifact : config.getArtifacts()) {
					System.out.println("Downloading " + artifact); // TODO: fix

					executorService.submit(new Runnable() {
						@Override
						public void run() {
							final File destination = layout.file(directory, resolveModule, artifact);
							try {
								destination.getParentFile().mkdirs();
								repository.get(transport, resolveModule, artifact, destination);
							} finally {
								synchronized (lock) {
									downloadedFiles.add(destination);
									count--;
									lock.notify();
								}
							}
						}
					});
				}
			}

			filterDependencies(context.getConfigurations(), resolveResult.getDependencies(), resolveQueue);
		}
	}

	private void filterDependencies(Set<String> configs, Collection<Dependency> dependencies, final Queue<ConfigurationContext> resolveQueue) {
		for (Dependency dependency : dependencies) {
			for (String configuration : configs) {
				final Set<String> configurations = dependency.getMapping().transform(configuration);
				if (configurations.isEmpty()) {
					// the dependency is not part of the current configuration
					break;
				}
				resolveQueue.add(new ConfigurationContext(dependency, configurations));
			}
		}
	}
}
